using System;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using CommunityServer;
using CommunityServer.Components;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using CommunityServer.Controls;

namespace RobertMcLaws.CommunityServerStuff.Controls
{

    /// <summary>
    /// Form control that implements support for creating/registering a new user.
    /// </summary>
    public class CreateUserForm : WrappedFormBase
    {
        protected AccountActivation activation;
        protected UserInvitation invitation = null;
        bool passportAuthentication = false;

        TextBox UserNameTextBox;
        TextBox PasswordTextBox;
        TextBox EmailAddressTextBox;
        IButton CreateButton;
        CheckBox AcceptAgreementCheckBox;
        HyperLink AcceptAgreementHyperLink;
        DropDownList TimeZoneDropDownList;
        CheckBox AllowSiteToContactCheckBox;
        CheckBox AllowSitePartnersToContactCheckBox;
        CustomValidator CreateUserCustomValidator;

        protected List<WrappedSubFormBase> SubForms;

        #region Properties

        /// <summary>
        /// Gets or sets the comma-separated list of SubForm IDs registered to this control.
        /// </summary>
        /// <value>The sub form ids.</value>
        public string SubFormIds
        {
            get { return ((string)ViewState["SubFormIds"]) ?? string.Empty; }
            set { ViewState["SubFormIds"] = value; }
        }

        /// <summary>
        /// Gets or sets the create user CustomValidator id.
        /// </summary>
        /// <value>The create user CustomValidator id.</value>
        public string CreateUserCustomValidatorId
        {
            get { return (string)ViewState["CreateUserCustomValidatorId"] ?? string.Empty; }
            set { ViewState["CreateUserCustomValidatorId"] = value; }
        }

        /// <summary>
        /// Gets or sets the user name TextBox id.
        /// </summary>
        /// <value>The user name TextBox id.</value>
        public string UserNameTextBoxId
        {
            get { return (string)ViewState["UserNameTextBoxId"] ?? string.Empty; }
            set { ViewState["UserNameTextBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the password TextBox id.
        /// </summary>
        /// <value>The password TextBox id.</value>
        public string PasswordTextBoxId
        {
            get { return (string)ViewState["PasswordTextBoxId"] ?? string.Empty; }
            set { ViewState["PasswordTextBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the email address TextBox id.
        /// </summary>
        /// <value>The email address TextBox id.</value>
        public string EmailAddressTextBoxId
        {
            get { return (string)ViewState["EmailAddressTextBoxId"] ?? string.Empty; }
            set { ViewState["EmailAddressTextBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the create IButton id.
        /// </summary>
        /// <value>The create IButton id.</value>
        public string CreateButtonId
        {
            get { return (string)ViewState["CreateButtonId"] ?? string.Empty; }
            set { ViewState["CreateButtonId"] = value; }
        }

        /// <summary>
        /// Gets or sets the accept agreement HyperLink id.
        /// </summary>
        /// <value>The accept agreement HyperLink id.</value>
        public string AcceptAgreementHyperLinkId
        {
            get { return (string)ViewState["AcceptAgreementHyperLinkId"] ?? string.Empty; }
            set { ViewState["AcceptAgreementHyperLinkId"] = value; }
        }

        /// <summary>
        /// Gets or sets the accept agreement CheckBox id.
        /// </summary>
        /// <value>The accept agreement CheckBox id.</value>
        public string AcceptAgreementCheckBoxId
        {
            get { return (string)ViewState["AcceptAgreementCheckBoxId"] ?? string.Empty; }
            set { ViewState["AcceptAgreementCheckBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the time zone DropDownList id.
        /// </summary>
        /// <value>The time zone DropDownList id.</value>
        public string TimeZoneDropDownListId
        {
            get { return (string)ViewState["TimeZoneDropDownListId"] ?? string.Empty; }
            set { ViewState["TimeZoneDropDownListId"] = value; }
        }

        /// <summary>
        /// Gets or sets the allow site to contact CheckBox id.
        /// </summary>
        /// <value>The allow site to contact CheckBox id.</value>
        public string AllowSiteToContactCheckBoxId
        {
            get { return (string)ViewState["AllowSiteToContactCheckBoxId"] ?? string.Empty; }
            set { ViewState["AllowSiteToContactCheckBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the allow site partners to contact CheckBox id.
        /// </summary>
        /// <value>The allow site partners to contact CheckBox id.</value>
        public string AllowSitePartnersToContactCheckBoxId
        {
            get { return (string)ViewState["AllowSitePartnersToContactCheckBoxId"] ?? string.Empty; }
            set { ViewState["AllowSitePartnersToContactCheckBoxId"] = value; }
        }

        /// <summary>
        /// Gets or sets the Password Placeholder Id
        /// </summary>
        /// <value>The Password Placeholder Id</value>
        public string PasswordPlaceholderId
        {
            get { return (string)ViewState["PasswordPlaceholderId"] ?? string.Empty; }
            set { ViewState["PasswordPlaceholderId"] = value; }
        }

        #endregion

        #region Attach Child Controls

        protected override void AttachChildControls()
        {
            if (Context.User.Identity.AuthenticationType.ToLower() == "passport")
                passportAuthentication = true;

            UserNameTextBox = CSControlUtility.Instance().FindControl(this, UserNameTextBoxId) as TextBox;
            PasswordTextBox = CSControlUtility.Instance().FindControl(this, PasswordTextBoxId) as TextBox;
            EmailAddressTextBox = CSControlUtility.Instance().FindControl(this, EmailAddressTextBoxId) as TextBox;
            CreateButton = CSControlUtility.Instance().FindButtonControl(this, CreateButtonId);
            AcceptAgreementCheckBox = CSControlUtility.Instance().FindControl(this, AcceptAgreementCheckBoxId) as CheckBox;
            AcceptAgreementHyperLink = CSControlUtility.Instance().FindControl(this, AcceptAgreementHyperLinkId) as HyperLink;
            TimeZoneDropDownList = CSControlUtility.Instance().FindControl(this, TimeZoneDropDownListId) as DropDownList;
            AllowSiteToContactCheckBox = CSControlUtility.Instance().FindControl(this, AllowSiteToContactCheckBoxId) as CheckBox;
            AllowSitePartnersToContactCheckBox = CSControlUtility.Instance().FindControl(this, AllowSitePartnersToContactCheckBoxId) as CheckBox;
            CreateUserCustomValidator = CSControlUtility.Instance().FindControl(this, CreateUserCustomValidatorId) as CustomValidator;

            if (UserNameTextBox == null
                || PasswordTextBox == null
                || EmailAddressTextBox == null
                || CreateButton == null
                || CreateUserCustomValidator == null)
                throw new InvalidOperationException("UserNameTextBoxId, PasswordTextBoxId, EmailAddressTextBoxId, CreateUserCustomValidatorId, and CreateButtonId must be valid controls to render a CreateUserForm");

            CreateButton.Click += new EventHandler(CreateButton_Click);

            CSContext csContext = CSControlUtility.Instance().GetCurrentCSContext(this.Page);

            if (TimeZoneDropDownList != null)
            {
                CSControlUtility.Instance().PopulateWithTimeZones(TimeZoneDropDownList);

                if (!Page.IsPostBack && TimeZoneDropDownList.Items.FindByValue(csContext.SiteSettings.TimezoneOffset.ToString()) != null)
                    TimeZoneDropDownList.Items.FindByValue(csContext.SiteSettings.TimezoneOffset.ToString()).Selected = true;
            }

            if (HasAgreement && !string.IsNullOrEmpty(csContext.SiteSettings.TermsOfServiceUrl) && AcceptAgreementHyperLink != null)
                AcceptAgreementHyperLink.NavigateUrl = csContext.SiteSettings.TermsOfServiceUrl;

            this.SubForms = new List<WrappedSubFormBase>();
            if (!string.IsNullOrEmpty(this.SubFormIds))
            {
                foreach (string id in this.SubFormIds.Split(','))
                {
                    WrappedSubFormBase subForm = CSControlUtility.Instance().FindControl(this, id) as WrappedSubFormBase;
                    if (subForm != null)
                    {
                        subForm.HostForm = this;
                        this.SubForms.Add(subForm);
                    }
                }
            }
        }

        #endregion

        #region Data Binding

        public override void DataBind()
        {
            base.DataBind();

            foreach (WrappedSubFormBase subForm in this.SubForms)
            {
                subForm.DataSource = this.DataSource;
                subForm.DataBind();
            }
        }

        #endregion

        #region Event Handlers

        void CreateButton_Click(object sender, EventArgs e)
        {
            if (!this.IsValid() || (CreateButton.CausesValidation && !this.Page.IsValid))
                return;

            foreach (WrappedSubFormBase subForm in this.SubForms)
            {
                if (!subForm.IsValid())
                    return;
            }

            User user = new User();

            #region Populate User

            user.Username = UserNameTextBox.Text;
            user.Email = EmailAddressTextBox.Text;
            user.Password = PasswordTextBox.Text.Trim();

            // Passport authentication
            if (passportAuthentication && Context.Items.Contains("PassportUID"))
            {
                user.AppUserToken = (string)Context.Items["PassportUID"];
                user.Password = user.AppUserToken;
            }

            // If we're using Email Account Activation, then we generate the password
            if (activation == AccountActivation.Email)
                user.Password = MemberRoleProfileProvider.Instance().Members.GeneratePassword(8, 2);

            // Does the user require approval?
            if (activation == AccountActivation.AdminApproval)
                user.AccountStatus = UserAccountStatus.ApprovalPending;
            else
                user.AccountStatus = UserAccountStatus.Approved;

            // Set the Anonymous flag to false
            user.IsAnonymous = false;

            #endregion

            foreach (WrappedSubFormBase subForm in this.SubForms)
            {
                subForm.ApplyChangesBeforeCommit(user);
            }

            CreateUserStatus status = CreateNewUser(user);
            if (status == CreateUserStatus.Created)
            {
                foreach (WrappedSubFormBase subForm in this.SubForms)
                {
                    subForm.ApplyChangesAfterCommit(user);
                }

                ProcessNewUser(user);
            }
            else
            {
                ErrorStatus(status);
            }
        }

        protected override void OnInit(EventArgs e)
        {
            base.OnInit(e);

            CSContext csContext = CSControlUtility.Instance().GetCurrentCSContext(this.Page);

            CSControlUtility.Instance().EnsureSecureConnection(this.Context);

            activation = csContext.SiteSettings.AccountActivation;
            if (csContext.User.IsMembershipAdministrator || csContext.User.IsAdministrator)
                activation = AccountActivation.Automatic;
            else
            {
                if (!csContext.SiteSettings.AllowNewUserRegistration)
                    throw new CSException(CSExceptionType.UserAccountRegistrationDisabled);

                if (activation == AccountActivation.InvitationOnly)
                {
                    invitation = CSControlUtility.Instance().GetCurrentUserInvitation(this.Page);
                    if (invitation == null)
                        throw new CSException(CSExceptionType.UserAccountRequiresValidInvitation);
                }
            }
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Creates the new user, returning the user creation status.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        protected CreateUserStatus CreateNewUser(User user)
        {
            // Attempt to create the user
            if (user == null || user.Username == "Anonymous")
            {
                return CreateUserStatus.DuplicateUsername;
            }
            else
            {

                try
                {
                    return Users.Create(user, true, CSControlUtility.Instance().GetCurrentCSContext(this.Page).User.IsAdministrator);
                }
                catch (CSException exception)
                {
                    return exception.CreateUserStatus;
                }
                catch (Exception ex)
                {
                    return MemberRoleProfileProvider.Instance().Members.GetCreateUserStatus(ex);
                }

            }
        }

        private void ErrorStatus(CreateUserStatus status)
        {
            switch (status)
            {

                // Username is disallowed
                case CreateUserStatus.DisallowedUsername:
                    CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_CreateUserStatus_DisallowedUsername");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                // Username already exists!
                case CreateUserStatus.DuplicateUsername:
                    CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_CreateUserStatus_DuplicateUsername");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                // Email already exists!
                case CreateUserStatus.DuplicateEmailAddress:
                    CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_CreateUserStatus_DuplicateEmailAddress");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                case CreateUserStatus.InvalidEmail:
                    CreateUserCustomValidator.ErrorMessage = ResourceManager.GetString("CreateNewAccount_CreateUserStatus_InvalidEmail");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                case CreateUserStatus.InvalidPassword:
                    CreateUserCustomValidator.ErrorMessage = ResourceManager.GetString("CreateNewAccount_CreateUserStatus_InvalidPassword");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                case CreateUserStatus.InvalidQuestionAnswer:
                    CreateUserCustomValidator.ErrorMessage = ResourceManager.GetString("CreateNewAccount_CreateUserStatus_QA");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                case CreateUserStatus.InvalidUserName:
                    CreateUserCustomValidator.ErrorMessage = ResourceManager.GetString("CreateNewAccount_CreateUserStatus_InvalidUserName");
                    CreateUserCustomValidator.IsValid = false;
                    break;

                // Unknown failure has occurred!
                case CreateUserStatus.UnknownFailure:
                    CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_CreateUserStatus_UnknownFailure");
                    CreateUserCustomValidator.IsValid = false;
                    break;

            }
        }

        /// <summary>
        /// Processes the new user.
        /// </summary>
        /// <param name="user">The user.</param>
        protected virtual void ProcessNewUser(User user)
        {
            CSContext csContext = CSControlUtility.Instance().GetCurrentCSContext(this.Page);

            SaveAdditionalProfileData(user);

            // the user was created, so the invitation, if it exists, has been accepted
            if (activation == AccountActivation.InvitationOnly && invitation != null)
                UserInvitations.Accept(invitation, user);

            switch (activation)
            {
                case AccountActivation.AdminApproval:
                    CSControlUtility.Instance().LeaveSecureConnection(Globals.GetSiteUrls().Message(CSExceptionType.UserAccountPending), this.Context);
                    break;

                case AccountActivation.Email:
                    CSControlUtility.Instance().LeaveSecureConnection(Globals.GetSiteUrls().Message(CSExceptionType.UserAccountCreated), this.Context);
                    break;

                case AccountActivation.Automatic:

                    // Are we allowing login?
                    if (csContext.SiteSettings.AllowLogin)
                        if (!Page.Request.IsAuthenticated)
                            FormsAuthentication.SetAuthCookie(user.Username, true);

                    CSControlUtility.Instance().LeaveSecureConnection(Globals.GetSiteUrls().Message(CSExceptionType.UserAccountCreatedAuto), this.Context);

                    break;

                case AccountActivation.InvitationOnly:

                    // Are we allowing login?
                    if (csContext.SiteSettings.AllowLogin)
                        if (!Page.Request.IsAuthenticated)
                            FormsAuthentication.SetAuthCookie(user.Username, true);

                    CSControlUtility.Instance().LeaveSecureConnection(Globals.GetSiteUrls().Message(CSExceptionType.UserAccountCreatedInvitationAccepted), this.Context);

                    break;
            }
        }

        /// <summary>
        /// Saves the additional profile data.
        /// </summary>
        /// <param name="user">The user.</param>
        protected virtual void SaveAdditionalProfileData(User user)
        {
            CSContext csContext = CSControlUtility.Instance().GetCurrentCSContext(this.Page);

            if (TimeZoneDropDownList != null)
                user.Profile.Timezone = double.Parse(TimeZoneDropDownList.SelectedItem.Value);

            if (csContext.SiteSettings.ShowContactCheckboxes)
            {
                if (AllowSiteToContactCheckBox != null)
                    user.AllowSiteToContact = AllowSiteToContactCheckBox.Checked;

                if (AllowSitePartnersToContactCheckBox != null)
                    user.AllowSitePartnersToContact = AllowSitePartnersToContactCheckBox.Checked;
            }

            SetExtendedUserData(user);

            Users.UpdateUser(user);
        }

        /// <summary>
        /// Sets the extended user data.
        /// </summary>
        /// <param name="user">The user.</param>
        private void SetExtendedUserData(User user)
        {
            foreach (string controlName in ExtendedUserData.Fields)
            {
                Control c = CSControlUtility.Instance().FindControl(this, controlName);
                if (c != null)
                {
                    string value = null;

                    if (c is TextBox)
                        value = ((TextBox)c).Text;
                    else if (c is CheckBox)
                        value = ((CheckBox)c).Checked.ToString();
                    else if (c is ListControl)
                        value = ((ListControl)c).SelectedValue;

                    if (value != null)
                        user.SetExtendedAttribute(c.ID, value);
                }
            }
        }

        public override bool IsValid()
        {
            CSContext csContext = CSControlUtility.Instance().GetCurrentCSContext(this.Page);

            // Reset the placeHolderValidator
            CreateUserCustomValidator.IsValid = true;

            if (!base.IsValid())
                return false;

            // Check for site services agreement
            if (HasAgreement)
            {
                if (!AcceptAgreementCheckBox.Checked)
                {
                    CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_ServicesAgreementRequired").Replace("'", @"\'");
                    CreateUserCustomValidator.IsValid = false;
                    return false;
                }
            }

            // Check the max length on the signature
            if (UserNameTextBox.Text.Length > csContext.SiteSettings.UsernameMaxLength || UserNameTextBox.Text.Length < csContext.SiteSettings.UsernameMinLength)
            {
                CreateUserCustomValidator.ErrorMessage = string.Format(ResourceManager.GetString("CreateNewAccount_UsernameLimits"), csContext.SiteSettings.UsernameMinLength.ToString(), csContext.SiteSettings.UsernameMaxLength.ToString());
                CreateUserCustomValidator.IsValid = false;
                return false;
            }

            if (!string.IsNullOrEmpty(csContext.SiteSettings.UsernameRegex) && !Regex.IsMatch(UserNameTextBox.Text, csContext.SiteSettings.UsernameRegex))
            {
                CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_usernameRegExValidator").Replace("'", @"\'");
                CreateUserCustomValidator.IsValid = false;
                return false;
            }

            if (!string.IsNullOrEmpty(csContext.SiteSettings.PasswordRegex) && !Regex.IsMatch(PasswordTextBox.Text, csContext.SiteSettings.PasswordRegex) && PasswordTextBox.Visible)
            {
                CreateUserCustomValidator.ErrorMessage = CommunityServer.Components.ResourceManager.GetString("CreateNewAccount_PasswordRegExValidator").Replace("'", @"\'");
                CreateUserCustomValidator.IsValid = false;
                return false;
            }

            string errorMessage = "";

            if (!passportAuthentication && PasswordTextBox.Visible)
            {
                switch (activation)
                {

                    case AccountActivation.Automatic:
                    case AccountActivation.AdminApproval:
                    case AccountActivation.InvitationOnly:
                        if (!Users.PasswordIsMembershipCompliant(PasswordTextBox.Text.Trim(), out errorMessage))
                        {
                            CreateUserCustomValidator.IsValid = false;
                            CreateUserCustomValidator.ErrorMessage = errorMessage;
                            return false;
                        }
                        break;
                }
            }

            return true;
        }


        #endregion

        /// <summary>
        /// Gets a value indicating whether this form has a new user agreement.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this form has a new user agreement; otherwise, <c>false</c>.
        /// </value>
        protected bool HasAgreement
        {
            get
            {
                return AcceptAgreementCheckBox != null && AcceptAgreementCheckBox.Visible;
            }
        }

    }
}

